=begin
 *
 * File: sif02_definitions.rb
 * S-expression Interpreter Framework (SIF).
 * Version 1.0
 *
 * SIF02: A functional language with a little more funtcionality than SIF01.
 * Supports the same special forms and primitive procedures as SIF01.
 * Additionally, it supports the dyfn special form (create function with dynamic
 * scope) and the following primitive procedures: +, -, *, /, %, =, <, not,
 * apply, eval, number?, list?, symbol?, and procedure?
 *
 * Copyright (C) 2006-2008 by Ariel Ortiz <ariel.ortiz@itesm.mx>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
=end

require 'sif01_definitions'

#--------------------------------------------------------------------
class DyFnNode < FnNode

  special_form "dyfn"

  def interpret(context)
    return DynamicScopeProcedure.new(@params, @body)
  end

end

#--------------------------------------------------------------------
class DynamicScopeProcedure

  def initialize(params, body)
    @params = params
    @body = body
  end

  def call(args, context)
    return @body.interpret(extend_context(@params, args, context))
  end

  def to_sexp
    return "<dynamic scope procedure>"
  end

end

#--------------------------------------------------------------------
Primitives.include('+') do |args|
  if not args.all? {|x| x.kind_of?(Numeric)}
    raise InterpreterError.new('+ expects numbers as arguments')
  else
    args.inject(0) {|accum, x| accum + x}
  end
end

#--------------------------------------------------------------------
Primitives.include('-') do |args|
  if args.empty?
    raise InterpreterError.new('- expects at least one argument')
  elsif not args.all? {|x| x.kind_of?(Numeric)}
    raise InterpreterError.new('- expects numbers as arguments')
  elsif args.length == 1
    -args.first
  else
    args.inject {|accum, x| accum - x}
  end
end

#--------------------------------------------------------------------
Primitives.include('*') do |args|
  if not args.all? {|x| x.kind_of?(Numeric)}
    raise InterpreterError.new('* expects numbers as arguments')
  else
    args.inject(1) {|accum, x| accum * x}
  end
end

#--------------------------------------------------------------------
Primitives.include('/') do |args|
  if args.empty?
    raise InterpreterError.new('/ expects at least one argument')
  elsif not args.all? {|x| x.kind_of?(Numeric)}
    raise InterpreterError.new('/ expects numbers as arguments')
  elsif args.length == 1
    0
  else
    args.inject {|accum, x| accum / x}
  end
end

#--------------------------------------------------------------------
Primitives.include('%') do |args|
  if args.length != 2
    raise InterpreterError.new('% expects two arguments')
  elsif not args.all? {|x| x.kind_of?(Numeric)}
    raise InterpreterError.new('% expects numbers as arguments')
  else
    args[0] % args[1]
  end
end

#--------------------------------------------------------------------
Primitives.include('=') do |args, ctx|
  if args.length < 2
    raise InterpreterError.new('= expects at least two arguments')
  else
    args.all? {|x| args.first == x} ? :t : []
  end
end

#--------------------------------------------------------------------
Primitives.include('<') do |args|
  if args.length < 2
    raise InterpreterError.new('< expects at least two arguments')
  elsif not args.all? {|x| x.kind_of?(Numeric)}
    raise InterpreterError.new('< expects numbers as arguments')
  else
    x = args.first
    result = :t
    args.rest.each do |i|
      if x >= i
        result = []
        break
      else
        x = i
      end
    end
    result
  end
end

#--------------------------------------------------------------------
Primitives.include('not', 'null?') do |args|
  if args.length != 1
    raise InterpreterError.new('not expects one argument')
  elsif args.first == []
    :t
  else
    []
  end
end

#--------------------------------------------------------------------
Primitives.include('apply') do |args, context|
  if args.length != 2
    raise InterperterError.new('apply expects two arguments')
  elsif !args.first.callable?
    raise InterperterError.new('first argument to apply must be a procedure')
  elsif !args[1].kind_of?(Array)
    raise InterperterError.new('second argument to apply must be a list')
  else
    args.first.call(args[1], context)
  end
end

#--------------------------------------------------------------------

Primitives.include('eval') do |args, ctx|
  if args.length != 1
    raise InterperterError.new('eval expects one argument')
  else
    Node.build(args.first).interpret(ctx)
  end
end

#--------------------------------------------------------------------
def define_type_predicate(name)
  Primitives.include(name) do |args|
    raise InterpreterError.new('%s expects one argument' % name) if args.length != 1
    yield(args.first) ? :t : []
  end
end

define_type_predicate('number?') {|x| x.kind_of?(Numeric)}
define_type_predicate('symbol?') {|x| x.kind_of?(Symbol)}
define_type_predicate('list?') {|x| x.kind_of?(Array) }
define_type_predicate('procedure?') {|x| x.callable? }

